/**
 * 像 Object 和 Array 这样的原生构造函数，运行时可以直接在执行环境中使用。当然也可以自定义构造函数，以函数的形式为 自己的对象类型定义属性和方法。
 */

/**
 * 使用构造函数代替 8.2.2 中的工厂函数
 * @param name
 * @param age
 * @param job
 * @constructor
 */
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
        console.log(this.name)
    }
}

let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg

/**
 * 特别重要:
 * ECMAScript 的构造函数就是能创建对象的函数。
 *
 * new 操作符调用构造函数会执行的操作:
 * 1. 在内存中创建一个新对象。
 * 2. 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
 * 3. 构造函数内部的 this 被赋值为这个新对象（即 this 指向新对象）。
 * 4. 执行构造函数内部的代码（给新对象添加属性）。
 * 5. 如果构造函数返回非空对象，则返回该对象；否则，返回刚创建的新对象。
 */

/**
 * 构造函数不一定要写成函数声明的形式。赋值给变量的函数表达式也可以表示构造函数
 */
let Person1 = function(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        console.log(this.name);
    };
}
let person11 = new Person1("Nicholas", 29, "Software Engineer");
let person21 = new Person1("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg
console.log(person11 instanceof Object); // true
console.log(person11 instanceof Person1); // true
console.log(person21 instanceof Object); // true
console.log(person21 instanceof Person1); // true

/**
 * 构造函数与普通函数唯一的区别就是调用方式不同。除此之外，构造函数也是函数。
 */
// 作为构造函数
// let person = new Person("Nicholas", 29, "Software Engineer");
// person.sayName(); // "Nicholas"
// // 作为函数调用
// Person("Greg", 27, "Doctor"); // 添加到 window 对象
// window.sayName(); // "Greg"
// // 在另一个对象的作用域中调用
// let o = new Object();
// Person.call(o, "Kristen", 25, "Nurse");
// o.sayName(); // "Kristen"

/**
 * 构造函数的问题
 * 构造函数的主要问题在于，其定义的方法会在每个实例上 都创建一遍。因此对前面的例子而言，person1 和 person2 都有名为 sayName()的方法，但这两个方 法不是同一个 Function 实例。我们知道，ECMAScript 中的函数是对象，因此每次定义函数时，都会
 初始化一个对象。
 */
// 逻辑上讲，这个构造函数实际上是这样的
function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = new Function("console.log(this.name)"); // 逻辑等价
}
// 下面的注释很重要
// 解决办法
// 虽然解决了相同逻辑的函数重复定义的问题，但全局作用域也因此被搞乱了，因为那个函数实际上只能在一个对象上调用。如果这个对象需要多个方法，那么就要在全局作用域中定义多个函数。这会导致自定义类型引用的代码不能很好地聚集一起。这个新问题可以通过原型模式来解决。
function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName() {
    console.log(this.name);
}
// let person1 = new Person("Nicholas", 29, "Software Engineer");
// let person2 = new Person("Greg", 27, "Doctor");
// person1.sayName(); // Nicholas
// person2.sayName(); // Greg

